package main

import (
	"fmt"
	"sync"
	"time"
)

/*
sync包

sync包提供了互斥锁。除了Once和WaitGroup类型，其余多数适用于低水平的程序，多数情况下，高水平的同步使用channel通信性能会更优一些。sync包类型的值不应被复制。

① sync.WaitGroup：在代码中生硬的使用time.Sleep肯定是不合适的，Go语言中可以使用sync.WaitGroup来实现并发任务的同步，需要注意sync.WaitGroup是一个结构体，传递的时候要传递指针。
   sync.WaitGroup有以下几个方法：

	1.1 (wg * WaitGroup) Add(delta int)：向 WaitGroup 中添加 n 个等待的 Goroutine；
	1.2 (wg *WaitGroup) Done()：表示一个等待的 Goroutine 已经完成了，向 WaitGroup 中减少一个等待的 Goroutine；
	1.3 (wg *WaitGroup) Wait()：等待所有添加到 WaitGroup 中的 Goroutine 都完成；

② sync.Once：在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次，例如只加载一次配置文件、只关闭一次通道等。sync.Once只有一个Do方法，其签名如下：
   func (o *Once) Do(f func()) {}。如果要执行的函数f需要传递参数就需要搭配闭包来使用。

TIPS：同步的sync是串行执行，异步的sync是同时执行
*/

var (
	x             int64
	wg            sync.WaitGroup
	lock          sync.Mutex
	rwlock        sync.RWMutex
	icons         map[string]string
	loadIconsOnce sync.Once
	instance      *singleton
)

func main() {

	/*
		普通开启两个goroutine去累加变量x的值，这两个goroutine在访问和修改x变量的时候就会存在数据竞争，因为同时操作一个资源（临界区），这种情况会发生竞态问题（数据竞态），
		导致最后的结果与期待的不符（非并发安全）。

		TIPS：GO语言提供了相关原子类，例如	atomic.AddInt64()，这里只是为了演示Go 中使用同步机制。
	*/
	wg.Add(2) //sync.WaitGroup之计数器+2
	go add()
	go add()
	wg.Wait() // sync.WaitGroup之阻塞直到计数器变为0
	fmt.Printf("非并发安全结果：%d\n", x)

	/*
		使用互斥锁（并发安全）

		互斥锁是一种常用的控制共享资源访问的方法，它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。

		使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区，其他的goroutine则在等待锁；当互斥锁释放后，等待的goroutine才可以获取锁进入临界区，
		多个goroutine同时等待一个锁时，唤醒的策略是随机的。
	*/
	x = 0
	wg.Add(2)
	go add1()
	go add1()
	wg.Wait()
	fmt.Printf("使用互斥锁（并发安全）结果：%d\n", x)

	/*
		读写互斥锁（并发安全）

		互斥锁是完全互斥的，但是有很多实际的场景下是读多写少的，当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的，这种场景下使用读写锁是
		更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

		读写锁分为两种：读锁和写锁。当一个goroutine获取读锁之后，其他的goroutine如果是获取读锁会继续获得锁，如果是获取写锁就会等待；当一个goroutine
		获取写锁之后，其他的goroutine无论是获取读锁还是写锁都会等待.如果读和写的操作差别不大，读写锁的优势就发挥不出来。
	*/

	x = 0
	start := time.Now()
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go write()
	}
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go write()
	}

	for i := 0; i < 3000; i++ {
		wg.Add(1)
		go read()
	}

	wg.Wait()
	end := time.Now()
	fmt.Printf("读写互斥锁（并发安全）结果：%d \n", x)
	fmt.Printf("读写互斥锁（并发安全）用时：%d", end.Sub(start))

	/*
		sync.Once加载配置文件示例

		见 Icon() 和 Icon1() 对比。
	*/

	/*
		sync.Once 并发安全的单例模式
		见 GetInstance() 函数
	*/

}

type singleton struct{}

/*
sync.Once 并发安全的单例模式
*/
func GetInstance() *singleton {

	loadIconsOnce.Do(func() {
		instance = &singleton{}
	})
	return instance
}

func loadIcons() {

	icons = map[string]string{
		"left":  "left.png",
		"up":    "up.png",
		"right": "right.png",
		"down":  "down.png",
	}
}

/*
多个goroutine并发调用Icon函数时不是并发安全的，因为现代的编译器和CPU可能会在保证每个goroutine都满足串行一致的基础上自由地重排访问内存的顺序。
并发安全的见Icon1() 函数使用的 sync.Once
*/
func Icon(name string) string {

	if icons == nil {
		loadIcons()
	}
	return icons[name]
}

/*
Icon 是并发安全的
*/
func Icon1(name string) string {

	// 使用 sync.Once
	loadIconsOnce.Do(loadIcons)
	return icons[name]
}

func add() {
	for i := 0; i < 1000; i++ {
		x = x + 1
	}
	wg.Done() // sync.WaitGroup之计数器-1
}

func add1() {

	for i := 0; i < 1000; i++ {
		lock.Lock() // 加锁
		x = x + 1
		lock.Unlock() // 解锁
	}
	wg.Done()
}

func write() {

	// lock.Lock()   		// 加互斥锁
	rwlock.Lock() // 加写锁
	x = x + 1
	time.Sleep(time.Millisecond) // 假设读操作耗时10毫秒
	rwlock.Unlock()              // 解写锁
	// lock.Unlock()                     	// 解互斥锁
	wg.Done()
}

func read() {

	// lock.Lock()                  // 加互斥锁
	rwlock.RLock()               // 加读锁
	time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
	rwlock.RUnlock()             // 解读锁
	// lock.Unlock()                // 解互斥锁
	wg.Done()
}
